﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace NJabber.Client
{
    public class RosterCache : IRosterCache
    {
        private IDictionary<JID, RosterItem> originalRoster; 
        private IDictionary<JID, RosterItem> localRoster;
        private IRosterSynchronizer synchronizer;
        private RosterChanges changes;

        public RosterCache(IRosterSynchronizer synchronizer)
        {
            if (synchronizer == null) throw new ArgumentNullException("synchronizer");

            this.synchronizer = synchronizer;
            changes = new RosterChanges(this);
            localRoster = new Dictionary<JID, RosterItem>();
            originalRoster = new Dictionary<JID, RosterItem>();
        }

        public RosterChanges Changes
        {
            get { return changes; }
        }

        public IEnumerable<RosterItem> AddedItems
        {
            get
            {
                return RosterListsDifference(localRoster, originalRoster);
            }
        }

        public IEnumerable<RosterItem> ChangedItems
        {
            get
            {
                return localRoster.Where(i => originalRoster.ContainsKey(i.Key))
                                  .Except(originalRoster)
                                  .Select(i => i.Value);
            }
        }

        public IEnumerable<RosterItem> RemovedItems
        {
            get
            {
                return RosterListsDifference(originalRoster, localRoster);
            }
        }

        private IEnumerable<RosterItem> RosterListsDifference(IDictionary<JID, RosterItem> rosterList1,
                                                              IDictionary<JID, RosterItem> rosterList2)
        {
            return rosterList1.Except(rosterList2).Select(kvp => kvp.Value);
        }

        public IEnumerable<RosterItem> Items
        {
            get { return localRoster.Values; }
        }

        public RosterItem this[JID itemJid]
        {
            get
            {
                if (itemJid == null) throw new ArgumentNullException("itemJid");
                if (!localRoster.ContainsKey(itemJid)) throw new InvalidOperationException();
                return localRoster[itemJid];
            }
        }

        public void Add(RosterItem item)
        {
            if (item == null) throw new ArgumentNullException("item");
            localRoster[item.Jid] = item;
        }

        public void Remove(JID itemJid)
        {
            if (itemJid == null) throw new ArgumentNullException("itemJid");
            if (!localRoster.ContainsKey(itemJid)) throw new InvalidOperationException();
            localRoster.Remove(itemJid);
        }

        public void AcceptItemChanges(JID itemJid)
        {
            if (itemJid == null) throw new ArgumentNullException("itemJid");
            if (localRoster.ContainsKey(itemJid)) originalRoster.Add(itemJid, localRoster[itemJid]);
            else if (originalRoster.ContainsKey(itemJid)) originalRoster.Remove(itemJid);
        }

        public void DiscardItemChanges(JID itemJid)
        {
            if (itemJid == null) throw new ArgumentNullException("itemJid");
            if (localRoster.ContainsKey(itemJid)) localRoster.Remove(itemJid);
            if (originalRoster.ContainsKey(itemJid)) localRoster.Add(itemJid, originalRoster[itemJid]);
        }

        public void Push(RosterItem item)
        {
            localRoster.Add(item.Jid, (RosterItem)item.Clone());
            originalRoster.Add(item.Jid, (RosterItem)item.Clone());
        }

        public void Sync()
        {
            synchronizer.Sync(Changes);
        }

        public void Reset()
        {
            localRoster.Clear();
            originalRoster.Clear();
        }

        public void Reset(IEnumerable<RosterItem> newRoster)
        {
            Reset();
            foreach (RosterItem item in newRoster) Push(item);
        }

        public void Invalidate()
        {
            localRoster.Clear();
            foreach (RosterItem item in originalRoster.Values) localRoster.Add(item.Jid, item);
        }
    }
}